#!/usr/bin/env python
#  Main.py
#  TurnShip
#
#  Created by Pete Lord on 22/12/2008.
#  Copyright (c) 2008 __MyCompanyName__. All rights reserved.
#

import os
import pickle
import pygame
import socket
import subprocess
import sys
import time
import zlib

import Effects
import Entities
import Graphics
import Interfaces
import Maps
import Options
import Orders
import Ships
import ToClient
import ToServer
import Views

pygame.init()
pygame.display.set_caption("TurnShip: Skies of Ourl","TurnShip")
pygame.display.set_icon(pygame.image.load(os.path.join('Data', 'Icon.png')))

class Server:
    """Wrapper around the socket object which automatically handles
    pickling and compression"""
    def __init__(self, addr):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Connect to the server
        self.socket.connect(addr)
        self.socket.settimeout(0)
        
    def send(self, data):
        print "Sending:", data.__class__.__name__
        self.socket.send(zlib.compress(pickle.dumps(data), 9))
        
    def recv(self, buffer_size):
        data = self.socket.recv(buffer_size)
        if data != '':
            data = pickle.loads(zlib.decompress(data))
            print "Received:", data.__class__.__name__
            return data
        return None

clock = pygame.time.Clock()

directions = ("north",
              "north east",
              "east",
              "south east",
              "south",
              "south west",
              "west",
              "north west")
              

#if len(sys.argv) > 1:
   #fullscreen = int(sys.argv[1])
#else:
   #fullscreen = 0
fullscreen = 0

def main():
    
    pygame.key.set_repeat(100, 30)
    
    # Filter out all events...
    pygame.event.set_allowed(None)
    # ...except those listed below
    pygame.event.set_allowed(
        (pygame.KEYDOWN,
         pygame.KEYUP,
         pygame.QUIT,
         pygame.VIDEORESIZE,
         pygame.MOUSEMOTION,
         pygame.MOUSEBUTTONDOWN,
         pygame.MOUSEBUTTONUP,
        ))
    
    ship_graphics = Graphics.ShipGraphics()
    tile_graphics = Graphics.TileGraphics()
    graphics = Graphics.Graphics()
    
    view = Views.GameView(fullscreen, tile_graphics)
    
    entities = Entities.EntityHandler(view, tile_graphics, ship_graphics, graphics)
    view.entities = entities
    
    cursor = Entities.Cursor(entities)
    entities.cursor = cursor
    
    try:
        host = sys.argv[1]
        port = int(sys.argv[2])
    except IndexError:
        host = 'localhost'
        port = 7059
    # Create a new TCP/IP socket
    try:
        server = Server((host, port))
        entities.interface.append(Interfaces.TimedBox(entities.interface, "Connected to " + host + ":" + str(port) + " successfully"))
    except socket.error:
        server_p = subprocess.Popen(["python", os.path.join("Server", "Server.py"), str(port)])
        time.sleep(0.5)
        server = Server(('localhost', port))
        entities.interface.append(Interfaces.TimedBox(entities.interface, "Could not connect to " + host + ":" + str(port) + ". Started a local server."))
    
    entities.server = server
    
    # Sync data from server
    data = None
    while isinstance(data, ToClient.Sync) is False:
        try:
            # Get the new data from server
            data = server.recv(4096)
            if data is None:
                raise SystemExit
            data(entities)
        # If no new data to come through
        except socket.error:
            # Sleep and try again
            time.sleep(0.01)
            
    entities.interface.append(Interfaces.TurnBox(entities.interface, entities))
    entities.interface.append(Interfaces.Compass(entities.interface, entities))
    
    # def addCloud(self, x, y, colour):
    # colour = (r, g, b) so... e.g. 
    #random pattern goes here btw
    #entities.addCloud(1, 1, (255,255,255))
    #entities.clouds[0].set_alpha_cross(32, 50, 255)
    #entities.clouds[0].set_alphas_cross([(32, 32, 150),(32, 33, 150),(33, 32, 150),(33,33,150),(34, 33, 150), (34, 34, 150)])

    input_grabbed = None
    
    try:
        while True:
            clock.tick(40)
            
            try:
                while True:
                    # Get the new data from server
                    data = server.recv(4096)
                    if data is None:
                        raise SystemExit
                    data(entities)
            except socket.error:
                # If no new data to come through
                pass
            
            # If we have a widget grabbing input
            if input_grabbed:
                # But it isn't in the parent any more (i.e, it's been
                # deleted)
                if not (input_grabbed in input_grabbed.parent):
                    # Release input grab
                    input_grabbed = None
            
            # If no widgets have grabbed input focus
            if input_grabbed == None:
                # Any keys events which you want to have repeat for should be set here
                for event in pygame.event.get(pygame.KEYDOWN):
                    if event.key == pygame.K_ESCAPE:
                        entities.deselect()
                        
                    if event.key == pygame.K_w:
                        entities.cursor.move(0, 1)
                    
                    if event.key == pygame.K_a:
                        entities.cursor.move(0, -1)
                    
                    if event.key == pygame.K_q:
                        entities.cursor.move(-1, 0)
                    
                    if event.key == pygame.K_s:
                        entities.cursor.move(1, 0)
                            
                    if event.key == pygame.K_e:
                        entities.night += 5
                        if entities.night > 255:
                            entities.night = 255
                    
                    if event.key == pygame.K_d:
                        entities.night += -5
                        if entities.night < 0:
                            entities.night = 0
                        
                    if event.key == pygame.K_n:
                        # End turn (next player)
                        server.send(ToServer.EndTurn())
                        
                    if event.key == pygame.K_SPACE:
                        entities.select()
                    
                    if event.key == pygame.K_i:
                        view.zoom(-1)
                        
                    if event.key == pygame.K_k:
                        view.zoom(1)
                        
                    if event.key == pygame.K_z:
                        # If we have a selected ship
                        if entities.selected:
                            server.send(ToServer.Fire(entities.selected, 0))

                    if event.key == pygame.K_x:
                        # If we have a selected ship
                        if entities.selected:
                            server.send(ToServer.Fire(entities.selected, 1))
                    
                    if event.key == pygame.K_t:
                        # Create a new TextInput and parent for typing into                        
                        saying = Interfaces.Horizontal(entities.interface, (0, 0, 0, 100))
                        saying.append(Interfaces.Text(saying, "Say:"))
                        saying.append(Interfaces.MessageInput(saying, server))
                        saying.rect.move_ip(100, 100)
                        entities.interface.append(saying)
                        # Set the input focus to the new TextInput
                        input_grabbed = saying[1]
                        del(saying)
                    
                # Any key events which should only happen once go here
                for event in pygame.event.get(pygame.KEYUP):
                    # If 'f' key is released
                    if event.key == pygame.K_f:
                        # Toggle fullscreen
                        view.toggle_fullscreen()
                        
            # If a widget has grabbed input focus
            else:
                # For each keydown event on the queue
                for event in pygame.event.get(pygame.KEYDOWN):
                    # Send the key event to the widget to be parsed
                    input_grabbed.event(event)
                # Clear all the keyup events so that they don't fill
                # up the event queue
                pygame.event.clear(pygame.KEYUP)

            for event in pygame.event.get(pygame.QUIT):
                raise SystemExit
                
            for event in pygame.event.get(pygame.VIDEORESIZE):
                Options.SCREEN_WIDTH, Options.SCREEN_HEIGHT = view.rect.size = event.w, event.h
                view.screen = pygame.display.set_mode((Options.SCREEN_WIDTH, Options.SCREEN_HEIGHT), view.screen.get_flags())
            
            # Work-around for multiple arrow-keys, not sure it will play
            # nicely with low framerate
            
            # Get the keys currently being pressed
            keys = pygame.key.get_pressed()
            if keys[pygame.K_UP]:
                view.pan_by(0, - (Options.KEY_SCROLL * Options.ZOOM_MOD[Options.ZOOM_LEVEL]))
            if keys[pygame.K_DOWN]:
                view.pan_by(0, + (Options.KEY_SCROLL * Options.ZOOM_MOD[Options.ZOOM_LEVEL]))
            if keys[pygame.K_LEFT]:
                view.pan_by(- (Options.KEY_SCROLL * Options.ZOOM_MOD[Options.ZOOM_LEVEL]), 0)
            if keys[pygame.K_RIGHT]:
                view.pan_by(+ (Options.KEY_SCROLL * Options.ZOOM_MOD[Options.ZOOM_LEVEL]), 0)
    
    # Start mouse code --------------------------------------------

            for event in pygame.event.get(pygame.MOUSEBUTTONDOWN):
                if event.button == 1:
                    # click
                    hovering = entities.interface.hovering(event.pos)
                    # If the mouse isn't hovering over any widgets
                    if hovering:
                        if entities.interface.clicking(event.pos):
                            # If widget wants to grab input focus, then
                            # stop taking events
                            input_grabbed = hovering
                        else:
                            input_grabbed = None
                    # If the mouse isn't hovering over any widgets
                    else:
                        # Release input focus
                        input_grabbed = None
                        # If player clicked within the map
                        # Move the cursor to mouse position
                        if entities.cursor.move_screen_xy(event.pos):
                            # If player is holding shift key
                            if pygame.key.get_mods() & pygame.KMOD_LSHIFT == pygame.KMOD_LSHIFT \
                            or pygame.key.get_mods() & pygame.KMOD_RSHIFT == pygame.KMOD_RSHIFT:
                                # If it's this player's turn
                                if entities.player.key() == entities.players.turn:
                                    try:
                                        # If it's this player's ship
                                        if entities.selected.player == entities.player.key():
                                                steam = 0
                                                x, y = entities.map.map_location(event.pos)
                                                r = entities.selected.get_direction_to_tile(x, y)
                                                angle = r - entities.selected.r
                                                if angle > 0:
                                                    if angle > 4:
                                                        # Turn port
                                                        angle = entities.selected.r - r
                                                        while angle > 0:
                                                            angle += -1
                                                            steam += 1
                                                    else:
                                                        # Turn starboard
                                                        while angle > 0:
                                                            angle += -1
                                                            steam += 1
                                                if entities.selected.steam >= steam:
                                                    server.send(ToServer.Rotate(entities.selected, r))
                                    except (TypeError, AttributeError):
                                        pass
                            # If player isn't holding shift key
                            else:
                                # Perform a select on tile
                                if entities.select():
                                    # If it's this player's turn
                                    if entities.player.key() == entities.players.turn:
                                        # If it's this player's ship
                                        if entities.selected.player == entities.player.key():
                                            entities.selected.path = [entities.cursor.pos()]
                    
                if event.button == 4:
                    view.pan_by(0, + (Options.MOUSE_SCROLL * Options.ZOOM_MOD[Options.ZOOM_LEVEL]))
                if event.button == 5:
                    view.pan_by(0, - (Options.MOUSE_SCROLL * Options.ZOOM_MOD[Options.ZOOM_LEVEL]))
                if event.button == 6:
                    view.pan_by( + (Options.MOUSE_SCROLL * Options.ZOOM_MOD[Options.ZOOM_LEVEL]), 0)
                if event.button == 7:
                    view.pan_by( - (Options.MOUSE_SCROLL * Options.ZOOM_MOD[Options.ZOOM_LEVEL]), 0)

            for event in pygame.event.get(pygame.MOUSEBUTTONUP): # Loop through all MOUSEBUTTONDOWN events on the buffer 1 = LMB, 2 = MMB, 3 = RMB 4 = upscroll, 5=obv, 7=lscroll, 8 = rscroll
                if event.button == 1:
                    try:
                        if len(entities.selected.path) > 1:
                            server.send(ToServer.Move(entities.selected, entities.selected.path[1:]))
                        del(entities.selected.path)
                    except AttributeError:
                        pass
            
            # If the player moves the mouse
            for event in pygame.event.get(pygame.MOUSEMOTION):
                # Get the widget under the mouse cursor
                hovering = entities.interface.hovering(event.pos)
                # If the mouse isn't hovering over any widgets
                if not hovering:
                    try:
                        del(entities.targets)
                    except AttributeError:
                        pass
                    # Move the cursor to mouse position
                    entities.cursor.move_screen_xy(event.pos)
                    try:
                        # If there isn't something in the way
                        if not entities.map.get_at_screen_xy(event.pos):
                            # If tile isn't already in the path
                            if not entities.cursor.pos() in entities.selected.path:
                                x, y = entities.selected.path[-1]
                                if abs(entities.cursor.x - x) <= 1 \
                                and abs(entities.cursor.y - y) <= 1:
                                    try:
                                        x, y = entities.selected.path[-2]
                                        # If going through a diagonal
                                        if abs(entities.cursor.x - x) == 1 \
                                        and abs(entities.cursor.y - y) == 1:
                                            # Delete the last position
                                            del(entities.selected.path[-1])
                                    except IndexError:
                                        pass
                                    # Add the current position
                                    entities.selected.path.append(entities.cursor.pos())
                                    steam = 0
                                    x, y, r = entities.selected.pos()
                                    path_x, path_y, path_r = entities.selected.pos()
                                    for x, y in entities.selected.path[1:]:
                                        r = entities.selected.get_direction_to_tile_relative(x - path_x, y - path_y)
                                        angle = r - path_r
                                        if angle > 0:
                                            if angle > 4:
                                                # Turn port
                                                angle = path_r - r
                                                while angle > 0:
                                                    angle += -1
                                                    steam += 1
                                            else:
                                                # Turn starboard
                                                while angle > 0:
                                                    angle += -1
                                                    steam += 1
                                        steam += 1
                                        path_x, path_y, path_r = x, y, r
                                    if steam > entities.selected.steam:
                                        del(entities.selected.path[-1])
                            try:
                                entities.interface.append(Interfaces.Tooltip(entities.interface, "SU: " + str(steam), event.pos))
                            except:
                                pass
                                        
                    except (AttributeError, TypeError):
                        pass
            
            # Possible optimisation: only take the last MOUSEMOTION event
            # per frame?
                
    # End mouse code   --------------------------------------------
            
            entities.poll()
            view.update()
            
            pygame.display.flip()

    except (SystemExit, KeyboardInterrupt):
        server.socket.close()
        try:
            server_p.terminate()
        except UnboundLocalError:
            pass
        pygame.quit()
    
if __name__ == "__main__": main()
